﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Emil.GMP;
using System.Security.Cryptography;
using NUnit.Framework;

namespace BYU_ECC
{
	public enum KeySizeEnum {
        Size140 = 140, Size192 = 192, Size224 = 224, Size256 = 256, Size384 = 384, Size521 = 521, Size1024=1024,
        Size2048 = 2048, Size3072 = 3072, Size7680 = 7680, Size15360 = 15360
	}
    
	class ParameterGenerator
    {
        #region ParameterGenerator Members and Properties
		private static KeySizeEnum keySize;
		public static KeySizeEnum KeySize {
			get {
				CheckGenerated();
				return keySize;
			}
		}

        private static BigInt a;
		public static BigInt A
        {
            get
            {
                CheckGenerated();
                return a;
            }
        }

		private static BigInt b;
		public static BigInt B
        {
            get
            {
                CheckGenerated();
                return b;
            }
		}

		private static BigInt p;
		public static BigInt P {
			get {
				CheckGenerated();
				return p;
			}
		}

		private static BigInt n;
		public static BigInt N {
			get {
				CheckGenerated();
				return n;
			}
		}

		private static Point basePoint;
		public static Point BasePoint {
			get {
				CheckGenerated();
				return basePoint;
			}
		}
        #endregion

        #region NIST Curve Generation

        public static void GenerateTestParameters(KeySizeEnum keySize) {
            ParameterGenerator.keySize = keySize;
            instance = new ParameterGenerator();
        }
        private ParameterGenerator() {
            switch (keySize) {
                case KeySizeEnum.Size192:
                    // Construct NIST P-192 Curve Parameters
                    p = new BigInt("6277101735386680763835789423207666416083908700390324961279");
                    n = new BigInt("6277101735386680763835789423176059013767194773182842284081");
                    b = new BigInt("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16);
                    basePoint = new Point(
                        new BigInt("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012", 16),
                        new BigInt("07192b95ffc8da78631011ed6b24cdd573f977a11e794811", 16));
                    break;
                case KeySizeEnum.Size224:
                    // Construct NIST P-224 Curve Parameters
                    p = new BigInt("26959946667150639794667015087019630673557916260026308143510066298881");
                    n = new BigInt("26959946667150639794667015087019625940457807714424391721682722368061");
                    b = new BigInt("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4", 16);
                    basePoint = new Point(
                        new BigInt("b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21", 16),
                        new BigInt("bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34", 16));
                    break;
                case KeySizeEnum.Size256:
                    // Construct NIST P-256 Curve Parameters
                    p = new BigInt("1157920892103562487626974469494075735300861434152903141955336313" +
                        "08867097853951");
                    n = new BigInt("1157920892103562487626974469494075735299969552241357603424222590" +
                        "61068512044369");
                    b = new BigInt("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
                    basePoint = new Point(
                        new BigInt("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
                        new BigInt("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16));
                    break;
                case KeySizeEnum.Size384:
                    // Construct NIST P-384 Curve Parameters
                    p = new BigInt("3940200619639447921227904010014361380507973927046544666794829340" +
                        "4245721771496870329047266088258938001861606973112319");
                    n = new BigInt("3940200619639447921227904010014361380507973927046544666794690527" +
                        "9627659399113263569398956308152294913554433653942643");
                    b = new BigInt("b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac" +
                        "656398d8a2ed19d2a85c8edd3ec2aef", 16);
                    basePoint = new Point(
                        new BigInt("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385" +
                        "502f25dbf55296c3a545e3872760ab7", 16),
                        new BigInt("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00" +
                        "a60b1ce1d7e819d7a431d7c90ea0e5f", 16));
                    break;
                case KeySizeEnum.Size521:
                    // Construct NIST P-521 Curve Parameters
                    p = new BigInt("686479766013060971498190079908139321726943530014330540939446345"+
                        "91855431833976560521225596406614545549772963113914808580371219879997166438" +
                        "12574028291115057151");
                    n = new BigInt("686479766013060971498190079908139321726943530014330540939446345" +
                        "91855431833976553942450577463332171975329639963713633211138647686124403803" +
                        "40372808892707005449");
                    b = new BigInt("051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef1" +
                        "09e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00", 16);
                    basePoint = new Point(
                        new BigInt("c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3" +
                        "dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66", 16),
                        new BigInt("11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e" +
                        "662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650", 16));
                    break;
                default:
                    break;
            }

            // Set standard parameters
            a = BigInt.NegativeThree;

            // Generate curve
            Curve.GenerateCurve(a, b, p);
        }

        #endregion

        #region Singleton Instance Functions
        private static ParameterGenerator instance;
        public ParameterGenerator Instance()
        {
            CheckGenerated();
            return instance;
        }

		public static void GenerateParameters(KeySizeEnum keySize)
        {
			ParameterGenerator.keySize = keySize;
            instance = new ParameterGenerator();
        }
		/*private ParameterGenerator()
        {
			// Choose parameters
			ChooseParameters();

			// Generate curve
			Curve.GenerateCurve(a, b, p);

			// Choose base point
			ChooseBasePoint();
		}*/

        #endregion

        #region Custom Curve Generation

        private void ChooseParameters() {
			// Choose p - the modulo
			// Using psuedo-Marsenne prime as recommended on Wikipedia
			p = BigInt.Two.Power(521) - 1;

			// Calculate v and w
			int l = p.BitLength; // bit length of p
			int v = (l - 1) / (int)keySize;
			int w = l - (int)keySize * v - 1;

			// Create bit mask of w size for later
			BigInt wMask = BigInt.Zero;
			for (int i = 0; i < w; i++)
				wMask = wMask.ChangeBit(i, 1);

			// Initialize vars
			BigInt c = BigInt.Zero;
			a = BigInt.Zero;
			b = BigInt.Two;

			// Create curve parameters
			do {
				do {
					Console.WriteLine("Generate parameters");
					// Generate initial values
					BigInt s = (BigInt.Two.Power((int)keySize) - 1).NextPrimeGMP(); // arbitrary keySize-bit string s
					SHA1 sha = SHA1.Create();
					BigInt h = new BigInt(sha.ComputeHash(s.ToByteArray())); // h = SHA-1(s)
					BigInt h0 = h.And(wMask); // take the w rightmost bits of h with the wMask made earlier
					BigInt z = s; // z = binary expansion of keySize-bit string s

					// Generate c
					c = h0;
					for (int i = 1; i <= v; i++) {
						BigInt si = (z.Add(i)).Mod(BigInt.Two.Power((int)keySize));
						BigInt hi = new BigInt(sha.ComputeHash(si.ToByteArray()));
						c = c.Or(hi);
					}
					Console.WriteLine("Parameters - s: " + s + "/" + s.BitLength + ", h: " + h + ", h0: " + h0 + "/" + h0.BitLength +
						", wMask: " + wMask + "/" + w + ", c: " + c);
				} while (c == 0 || (4 * c + 27).Mod(p) == 0); // Check conditions

				// Choose a, b in GF(p)
				// Such that c*b^2 (mod p) = a^3 (mod p)
				do {
					//TODO Going with easiest way right now, should change to recommendations on the right
					a = c;	// a should = -3 according to NIST recommendation
					b = c;	// b^2c mod p = -27 mod p with a=-3, but for now both are 'c' to make computation easy
					Console.WriteLine("a: " + a + ", b: " + b);
				} while ((c * b.Power(2)).Mod(p) != a.Power(3).Mod(p));

				FindOrder();
			} while (false);	//TODO Check for suitable order
		}

		/// <summary>
		/// Chooses a base point on the generated curve.
		/// </summary>
		private void ChooseBasePoint() {
			//TODO Generate base point, that works.  This doesn't.
			do {
				BigInt x = GenerateRandomBigInt(p);
				for (int i = 0; i < 32; i++) {
					basePoint = new Point(x, Curve.getYfromX(x));
					if (Curve.IsValidPoint(basePoint))
						break;
				}
				Console.WriteLine("X Generated: " + x);
			} while (!Curve.IsValidPoint(basePoint));
		}

		/// <summary>
		/// Finds the order of the generated curve and store in n.
		/// Uses Hasse's theorem currently, but could be modified to use Schoof's algorithm,
		/// which is difficult to implement.  Keep in mind this is just a bound, or estimate, of
		/// the actual N of the curve, or #E(Fq).
		/// </summary>
		private void FindOrder() {
			//r = p + 1 - 2 * p.Sqrt();
		}

		#endregion

		private static void CheckGenerated()
        {
            if (instance == null)
                throw new ParameterGeneratorNotGeneratedException();
        }

		public static BigInt GenerateRandomBigInt(BigInt maxSize) {
			StringBuilder builder;
			Random rand = new Random(DateTime.Now.Millisecond);
			BigInt ret;
            int size = maxSize.ToString().Length;
			do {
				builder = new StringBuilder();
				int max = rand.Next(size/2, size);
				for (int i = 0; i < max; i++) {
					builder.Append((char)rand.Next('0', '9' + 1));
				}
				ret = new BigInt(builder.ToString());
			} while (ret >= maxSize);
			return ret;
		}
    }

	class ParameterGeneratorNotGeneratedException : Exception { }


	[TestFixture]
	class ParameterGeneratorTests {
		[SetUp]
		public void SetUp() {

		}

		[TearDown]
		public void TearDown() {

		}

		[Test]
		public void TestGeneratedCurve() {

		}

		[Test]
		public void TestNISTCurve() {
			ParameterGenerator.GenerateTestParameters(KeySizeEnum.Size192);
			Console.WriteLine("N: " + ParameterGenerator.N);
		}

	}
}
